home *** CD-ROM | disk | FTP | other *** search
/ FishMarket 1.0 / FishMarket v1.0.iso / fishies / 076-100 / disk_098 / hddriver / article < prev    next >
Text File  |  1992-05-06  |  29KB  |  629 lines

  1. This was written for a local newsletter. Any people mentioned
  2. are locals.
  3.  
  4.         Alan Kent
  5.         22 Wallabah St
  6.         Mt Waverley 3149
  7.  
  8. Alan Kent     (RMIT, Melbourne, AUSTRALIA)
  9. UUCP: {seismo,hplabs,mcvax,ukc,nttlab}!munnari!goanna.oz!ajk
  10. ARPA: munnari!goanna.oz!ajk@SEISMO.ARPA
  11. ACSnet: ajk@goanna.oz
  12. ]
  13.  
  14.  
  15.                AN INTRODUCTION TO WRITING
  16.                  YOUR OWN DEVICE DRIVERS
  17.  
  18. Ron Wail and co. are not the only ones who have been
  19. working on a hard disk for the
  20. amiga. I have also been working on one for some time now
  21. in the little spare time I
  22. have. As I am currently doing my masters
  23. in computer science at RMIT, I
  24. do not have as much time as I would like to spend on the machine.
  25. If you wish to buy a hard disk you will have to buy one off Ron and
  26. friends as I have no intension of building and selling them.
  27. If you want to take on the challenge of building your own,
  28. then read on!
  29.  
  30. Getting the additional hardware needed to add a hard disk
  31. to work was trivial compared
  32. to the problems I had trying to get the software to work (this
  33. may be because my brother John designed the hardware for me).
  34. I used a WD-1002-05 controller card as my brother had one lying
  35. around at home. I recently rang Daneva Australia in Sandringham
  36. and they said the boards now cost approximately $550 plus 20% tax
  37. (single quantities). Perhaps there are cheaper boards around these days.
  38. The controller card can in fact handle 3 hard disks and 4 five
  39. and a quarter inch floppy drives so you can also add some standard
  40. 5 inch floppies too if you like.
  41. Using a prebuilt controller card made the hardware much simpler -
  42. in fact, only 7 additional IC's were needed
  43. for decoding and timing. At present I have the IC's on a wire
  44. wrap board which plugs into the expansion slot on the side of the
  45. amiga. Someday I hope to add a bit more memory and a battery backup
  46. clock. I will explain the hardware in more detail later.
  47.  
  48. In this article, I thought I would explain some of the
  49. fundamentals of trying to write a device driver. It may not be
  50. enough for you to write your own driver immediately,
  51. but its a step in the right direction.
  52. Much of the information here is relevant for
  53. any device driver, not just a hard disk device driver.
  54. All of the information in this
  55. article came from the manuals (somewhere), mainly the two 
  56. volumes of the RKM (Rom Kernal Manual). Much of the hardware
  57. information I used came from the expansion specs which I ordered
  58. from Commordore in the states for US$40(?).
  59. As I am a C programmer by nature and dislike having to write
  60. assembly language code (although it is faster), I have
  61. added an extra twist to this project of trying to implement my drivers in C.
  62. I think I should point out at this stage that it is almost impossible
  63. to add hard disks to V1.1 (I tried for weeks, but I just could not
  64. get AmigaDOS to realise that the hard disk existed)
  65. It is very easy however with V1.2.
  66.  
  67.                     THE EXEC AND AMIGADOS
  68.  
  69. Before we can really get into device drivers, some basics of
  70. the multitasking executive (called the exec) must be known. The exec
  71. is the low level program that has a number of responsibilities, the
  72. main responsibility being to decide which task is to
  73. run next on the amiga. A task is simply an occurance of a program
  74. running. For example, if two CLI windows are open at the same time,
  75. then there are two CLI tasks running.
  76. Each task on the amiga is given its own memory space and stack space.
  77. Details about the task are kept in a task structure such as where
  78. its stack space is and the contents of the task's registers
  79. when the task is not running.
  80. The exec also provides mechanisisms for supporting communication
  81. between tasks, suppoirt for libraries of functions and support for devices
  82. as well as keeping track of what memory has been allocated.
  83.  
  84. AmigaDOS is not part of the exec. It is another level of software
  85. which runs above the exec. AmigaDOS is basically a file management system
  86. which uses the exec functions. Its job is to keep
  87. track of where files reside on the disk and provide
  88. routines to read from and write to files.
  89. When AmigaDOS starts running a program, it actually starts a
  90. task, but adds some more information to the end of the task
  91. structure such where as to direct console input and output.
  92. Such tasks are called processes. Another slight difference is
  93. that a pointer to a process points to the first field in 
  94. AmigaDOS's extra information after the task structure. The first entry
  95. happens to contain a pointer to a message port which AmigaDOS
  96. uses to communicate with the process (more on message ports later).
  97. Care must be taken to determine
  98. if a pointer is a pointer to a process or a task. In C, a process pointer
  99. can easily be converted to a task pointer using the following code.
  100.  
  101.     task = (struct Task *) ( ((char *)process) - sizeof(struct Task) )
  102.  
  103.                         SIGNALS
  104.  
  105. For one task to communicate to another task, the exec
  106. provides a number of functions. Each task keeps 32 signals.
  107. These signals can be set by other tasks using the Signal() function.
  108. The task receiving the signal can then use the Wait() function
  109. to wait for a signal to arrive.
  110. When the Wait() function is called, the exec will remove
  111. the task from the list of running tasks
  112. until the desired signal arrives. This means that
  113. no CPU time is used by the waiting process allowing more CPU time
  114. for other processes.
  115. A task can actually wait for any one of many different signals
  116. to arrive from many different tasks at the same time.
  117. Signals thus provide a very simple
  118. synchronisation method between tasks. One problem with
  119. signals however is that if the same signal is sent twice
  120. to a task before the
  121. receiving task gets a chance to have a look, it appears
  122. as if only one signal is received.
  123.  
  124.                         MESSAGES
  125.  
  126. Signals by themselves are not particularly useful. What is more
  127. useful is to be able to send some information from
  128. one task to another. Messages and Ports provide this
  129. ability. A message is simply a block of memory which has
  130. a special structure at the beginning of the block followed
  131. by any other information that is to be sent.
  132. In C, this can be achieved by defining a structure as follows:
  133.  
  134.     struct my_message {
  135.                 /* first the standard message information */
  136.         struct Message msg;
  137.                 /* and now my details */
  138.         int number;
  139.         char character;
  140.         int other_information;
  141.         char *string;
  142.     };
  143.  
  144. The message structure definition can be found in the include
  145. file <exec/ports.h>. The Message structure has to be set up
  146. in a special way which is described in the RKM exec manual,
  147. page 34. Signals are used to notify a task that a new message
  148. has been received.
  149.  
  150.                         PORTS
  151.  
  152. Ok, now we have a message structure, but how do we get a
  153. message from one task to another? And how does one task know
  154. which of the 32 signals to send to the other task to notify it
  155. that a message has been sent? This is where ports become useful.
  156. A port is like putting a letter box outside your house. It provides
  157. a well known place that messages can be received.
  158. A port can be created using
  159. the function CreatePort() and when created, can be
  160. assigned a unique name. The function FindPort() can be used
  161. to search the whole system for a port by its name. When a port is created,
  162. it is also allocated a signal number which is used to tell
  163. the receiving task that a message has arrived.
  164.  
  165.                     SENDING MESSAGES
  166.  
  167. To send a message from one task to another then involves using the
  168. PutMsg() function. PutMsg() requires a pointer to the
  169. port (as returned by FindPort) and a pointer to the message
  170. to be sent. The actual sending of the message involves
  171. letting the receiving message port know where the message is in memory
  172. and then sending the receiving task a signal to let it know
  173. that a message has arrived. In order to allow many messages
  174. to be sent to a single port, messages are kept in a queue.
  175. This is why some special information is needed at the
  176. beginning of each message structure - list pointers must be
  177. maintained.
  178.  
  179. The receiving task normally waits for messages to arrive
  180. using the WaitPort() function. This basically involves doing
  181. a Wait() for the signal bit assigned to that port. Once a
  182. message is received (or if a message has already been
  183. received and the program had not noticed it yet) WaitPort()
  184. will exit. Messages can then be removed form the port using
  185. the GetMsg() function. Note that due to the problem
  186. mentioned before about signals and the possibility of
  187. multiple signals appearing to be only a single signal, many
  188. calls to GetMsg() may be required. It is also possible that
  189. WaitPort() will return that a new message has arrived but it
  190. had already been processed - its just that the signal bit
  191. had not yet been cleared. The normal code structure for
  192. receiving messages can be safely performed using the
  193. following C code:
  194.  
  195.     struct Message *msg;
  196.  
  197.     while ( 1 ) {        /* loop forever */
  198.         WaitPort ( message_port );
  199.         while ( ( msg = GetMsg ( message_port ) ) != NULL ) {
  200.             
  201.             /* do something with the message! */
  202.         
  203.         }
  204.     }
  205.  
  206. Until a message has been received and processed, the sending
  207. task should not modify the contents of the message. The
  208. PutMsg() call is basically granting the receiving task
  209. permission to use that piece of memory.
  210. Once the receiving task has finished
  211. with the message however, it may be useful to let the sending task
  212. know. This is in fact very important if information is to be
  213. returned to the sending task in the message structure. What
  214. is done then is for the sending task to also create a
  215. message port. This message port however is not given a name
  216. and so cannot be found using FindPort(). Instead, a pointer
  217. to this port is kept in the actual message structure
  218. (in the field mn_ReplyPort). The task that receives the message
  219. then uses the ReplyMsg() function to effectively send the
  220. message back to the original task. This means the the
  221. sending task must do a WaitPort() on the reply port so that
  222. it will wait until the message is sent back. This allows complete
  223. synchronization between the two tasks. Note that if the
  224. reply port is not put into the message structure, then
  225. ReplyMsg() does nothing.
  226.  
  227. To close off a port so that no more messages can be
  228. received, use DeletePort(). The functions CreatePort() and
  229. DeletePort() are actually support functions written in C which
  230. should be in the libraries that come with your C compiler. These
  231. functions allocate/deallocate the necessary memory and
  232. call (if necessary) the functions AddPort() and RemPort().
  233. For more details on ports and some
  234. example code using them, see chapter 3 in RKM: exec.
  235.  
  236.                 WHAT IS A DEVICE DRIVER?
  237.  
  238. Well, now lets get into what this article is really about!
  239. A device driver is a piece of code which provides an
  240. interface between the amiga operating system or user
  241. programs and (usually) hardware on the system. There is one
  242. device driver for controlling the floppy disks, another for
  243. controlling the printer and so on. Drivers usually reside on
  244. disk until such time as they are opened. When a device
  245. is opened, the amiga searches through its list of known
  246. devices in memory and if it is not found, it then searches
  247. the devs directory on disk. This is why the first time you
  248. use the printer, the disk will become active for a period of
  249. time (to load the device driver) but when using the printer
  250. later, there may not be any extra disk activity (as the
  251. driver is already loaded). Note that the amiga can try to
  252. reclaim memory from a device driver if it is not in use and
  253. the amiga is low on free memory. Using C it is not easy to
  254. get this auto-load feature to work. As a result, I decided
  255. to ignore it by instead adding a RUN command in my
  256. startup-sequence file so that the driver was permanently
  257. loaded and could not be removed. This may not be very elligent,
  258. but it works!
  259.  
  260. In order
  261. for a program to make a request to a device driver (for
  262. example the printer driver), an OpenDevice() call must be
  263. made. This
  264. call has 4 parameters: the name of the device you wish to
  265. open, the unit number you want (some devices such as the
  266. floppy disks can have several units - for efficency they all share
  267. the same device driver code), an IO request block
  268. and some flags which are interpreted by the
  269. device driver. The IO request block is a message with some extra
  270. information added (see struct IORequest in <exec/io.h>).
  271. This extra information includes a pointer to the device, a
  272. pointer to special memory allocated per unit, a command
  273. field, an error field and a flags field. As many devices
  274. perform much the same sort of commands (for example, writing
  275. to disk is not that much different than writing to a
  276. printer) a standard form of requests has been defined. The
  277. structure and basic commands defined are also in
  278. <exec/io.h>.
  279.  
  280. Once a device is open, requests can be made using the IO
  281. request block passed to the OpenDevice() call using the
  282. functions CheckIO(), DoIO(), SendIO() and WaitIO().
  283. DoIO() performs an IO operation by sending the device driver
  284. the IO request as a message and waiting for the driver to
  285. complete (signaled by a ReplyMsg()). To be more precise, DoIO()
  286. tries to call the BeginIO entry point in the device driver directly
  287. (explained later) which will either
  288. immediately process the command and return, or else send
  289. the message to the port for processing when the driver is ready
  290. for it. SendIO() sends the request to the device driver's port,
  291. but does not wait for completion. This allows the
  292. task to continue doing other things. WaitIO() can then be
  293. used to wait for the command to complete. CheckIO()
  294. provides a means of testing if the operation is complete
  295. without waiting for it if it has not. When CheckIO() says
  296. the operation is complete, WaitIO() must still be called to
  297. clear the signal bit. The RKM: libraries and devices
  298. contains many pieces of example code opening devices,
  299. sending commands and closing devices. Note that it is very
  300. important to close a device when finished with it as some of
  301. the devices only allow one task to open them at a time.
  302.  
  303.                 WRITING A DEVICE DRIVER
  304.  
  305. So now that we know what a device driver is, how do we go
  306. about writing one? In the RKM: libraries and devices
  307. appendix F a sample device driver written in assembler is
  308. shown. Looking at the code, I suspect it contains a few errors.
  309. Some pieces of code just didn't make sense to me. After
  310. looking at it for a while and (hopefully) understanding how
  311. it works, I threw it away and started again from scratch.
  312. The example in the manual is meant to be able to
  313. automatically load from disk. As I mentioned before, I
  314. did not worry about this as I wanted to get something
  315. working as quickly as possible and automatically loading C
  316. programs looked like it could be a bit of a problem
  317. (actually, one of the manuals stated it was not possible).
  318.  
  319. When writing a device driver, there are two main sections of code.
  320. The first section looks very much like a library - and in
  321. fact shares many structures and functions with libraries.
  322. This provides a nice consistant interface between the device
  323. driver and other programs that wish to use it.
  324. The second section is what actually performs all the
  325. commands.
  326.  
  327. First, a set of routines must be written which allow a
  328. device to be opened, closed and expunged (totally removed from
  329. memory). These are exactly the same as for a library. On top
  330. of these functions, two extra functions must be defined - BeginIO
  331. and AbortIO. BeginIO is a call which tries to
  332. immediately perform the IO operation without using the
  333. message port. If the operation cannot be done immediately,
  334. then the command must be queued by sending it to the message
  335. port. This allows simple status commands to be performed very
  336. quickly. The AbortIO command tries to abort an existing
  337. command. One problem with writing a driver in C code is that
  338. the exec passes parameters for these calls in registers.
  339. This means that some pieces of assembler must be written
  340. to push the registers onto the stack before calling the C code.
  341. Note that as the operating system is calling functions written
  342. in C directly, if the C compiler requires special values in
  343. the registers then the code will not work. For example, I have
  344. heard that the Aztec C compiler uses a register to point to the
  345. global variables. If this is the case, then this register must
  346. be set up (probably by the same code that pushes the registers
  347. onto the stack) before the actual C function can be called.
  348.  
  349. Once these routines are written a device structure
  350. (struct Device) must be created using the MakeLibrary()
  351. call. MakeLibrary() accepts a pointer to an area of memory
  352. that defines how the library node is to be initialized (see
  353. the function InitStruct()). However it is very difficult to
  354. set up the necessary memory area for InitStruct() in C and so I simply
  355. initialize the device structure after calling MakeLibrary()
  356. using normal C code.
  357. Once this has been done, AddDevice() must be called to
  358. add the new device to the system. Once AddDevice() has been
  359. called, other programs may use all of the device function
  360. calls previously mentioned (OpenDevice() etc).
  361.  
  362. The example device driver in the manual
  363. creates a new process for each unit. Rather than trying to
  364. make more problems for myself by trying to create new
  365. processes from a C program, and as I only have a single unit
  366. anyway, I did not create a new process per unit but rather
  367. used the same process as creates and adds the device node
  368. to receive and process commands.
  369. So, before the device is actually added to the system, a
  370. port must be created to recieve commands. The Open code will
  371. put a pointer to this port into the IO request block which
  372. was passed in the OpenDevice() call.
  373. This is how DoIO() and SendIO() know where to send the message.
  374.  
  375.                 THE HARD DISK DEVICE DRIVER
  376.  
  377. The above discussion should be able to be used for any type of device
  378. driver you wish to write. I have been considering trying to
  379. write a printer spooler using device drivers too, although
  380. surely someone has done a printer spooler for the amiga
  381. before (I had better go search through all the PD disks I guess).
  382.  
  383. The hard disk driver must emulate the trackdisk device driver in
  384. every way. Legal requests are defined in chapter 7 of
  385. the RKM: libraries and devices and include such commands as
  386. read data from disk, write data to disk, format a track,
  387. switch the motor on and so on. The commands are
  388. all fairly straight forward. The
  389. hard disk driver is much simpler than the floppy disk driver
  390. in many ways as the disk cannot
  391. be removed!
  392.  
  393. One area I found difficult to process was the
  394. sector labels. The trackdisk device actually allows an extra
  395. 16 bytes per sector to be written due to some sector header
  396. information on the disk. The hard disk controller I have
  397. does not allow this. As a result, I mark any commands that
  398. try to use this information as an error. So far, I have not
  399. found any code that actually uses this information so it
  400. is not a problem.
  401.  
  402. The trackdisk device driver (in V1.1 anyway)
  403. buffers a whole track of the disk in memory at a time.
  404. This buffering of data in memory is referred to as caching.
  405. V1.2 does better caching, but I dont know how (I dont have
  406. all the necessary documentation for V1.2).
  407. As a result, I tried to add some of my own more intelligent
  408. caching. Unfortunately, my caching seems to work quite well
  409. for about 5 minutes, but then it hangs the system. I will
  410. have to recheck the code sometime. It is difficult to
  411. determine however if the caching actually improves the speed of the
  412. hard disk or whether it runs slower due to the extra code
  413. that needs to be executed. The extra caching certainly uses
  414. up more memory, so at this stage I have removed all caching.
  415. Even without caching, its still faster than floppies.
  416.  
  417. My hard disk driver then consists of a number of sections.
  418. First, when it is loaded it creates and adds the device node
  419. to the system. It then waits for comands to arrive from a
  420. message port. When a command arrives, a switch statement
  421. decides what code to execute based on the command type.
  422. The disk read and write type commands map the offset and
  423. length fields in the command message onto head, track and
  424. sector numbers which is then fed to the hard disk controller.
  425. Other commands such as motor on/off and disk change count
  426. can be easily immitated (the motor can never be switched off
  427. and the disk change count is constant).
  428.  
  429. One area that can be confusing is that
  430. the code that is needed for handling open and close device
  431. calls made by programs making requests to the driver is
  432. never executed by the device driver process. When the device
  433. is added to the system, a set of pointers to these functions
  434. is put in the device structure allowing other tasks to execute
  435. code.
  436.  
  437. To install the driver involves adding a few lines to your
  438. S:STARTUP-SEQUENCE file. First the command RUN L:HARDDISK
  439. (where L:HARDDISK is my device driver program) starts up
  440. the device driver. Next the command MOUNT DH0: notifies
  441. AmigaDOS that the device can be used as a disk. For the
  442. mount command to work, the file DEVS:MOUNTLIST must have
  443. an entry for DH0: added. The file on my disk had an example entry
  444. for DF1: so I just copied it and changed the fields (such
  445. as number of cylinders, heads etc) for my hard disk.
  446. The name of the device driver also had to be changed from
  447. trackdisk.device to harddisk.device. I have also added
  448. to my S:STARTUP-SEQUENCE file
  449. some ASSIGN commands to change the C: directory to be on
  450. the hard disk. These changes need
  451. only be made once and then every time you boot up, you
  452. have a very big disk drive!
  453. One problem I did have however was to make sure that the
  454. hard disk driver was actually loaded before I started using
  455. it. This is because the RUN command exits before the program
  456. to be run has even been loaded from disk.
  457. After the RUN command I put in a delay of about 5 seconds
  458. in the S:STARTUP-SEQUENCE file
  459. so that the driver should have time to load completely.
  460.  
  461.                         HARDWARE
  462.  
  463. This is a brief description of the hardware I have used.
  464. It does not auto-configure (oh dear, Commodoore will never
  465. support me now) as I could not work out exactly how to
  466. do it. I have a copy of the expansion specs from Commodoore
  467. (I sent off to the states) but the auto-configure was a bit
  468. beyond the effort I wanted to go to. I am not likely to
  469. add anything else to my amiga anyway (famous last words).
  470. The controller card I used has 8 registers which I have mapped into
  471. memory space. Due to
  472. the 68000's 16 bit words, it ended up using 16 bytes of memory,
  473. the high bytes of the 16 bit words not being used.
  474. The controller's registers can be
  475. read from or written to and include cyclinder number registers, 
  476. status registers, command registers and so on. Sending commands
  477. to the hard disk simply involes storing the relevant parameters
  478. in the controllers registers and sending a command such as
  479. read a sector, write a sector or format a track.
  480. All this is done by simple memory read and write commands.
  481. I ended up putting the controller card at the top of expansion
  482. memory at $9ffff0 (actually I do not decode the address to that
  483. precision, but its close). As long as I dont expand to 8 megs
  484. of memory, it should not interfere with anything - even other
  485. boards which do autoconfigure.
  486.  
  487. After sending the controller a command, the device driver must
  488. wait for the command to finish. I tried to use the interrupt line
  489. provided by the controller card, but at this stage I have not
  490. successfully got the interrupt handlers on the amiga to work. The approach
  491. I am currently using is to poll the status register of the controller.
  492. Polling is where the program sits in a loop continuously checking
  493. the status register until the command has finished. In order to
  494. give other tasks a chance to run while the driver is polling, I
  495. placed a call to Delay() inside the polling loop. The delay cannot
  496. be too long or else the disk will become too slow, but it does give
  497. other tasks a bit of a chance to run.
  498.  
  499. Looking at the side of the amiga, the expansion bus is numbered
  500. as follows. Reading the hardware reference manual seems to say
  501. the numbering is different to this, but this is what I used (and
  502. it works). I would check the signal numbers before building this
  503. circuit in case of incorrect pin numbers due to typing errors.
  504.  
  505.  
  506.         1 3 5 7 9 ...... 85
  507.         ===================     the edge of the board
  508.         2 4 6 8 10 ..... 86
  509.     
  510.  
  511. WARNING: I do not take any responsibility if the following circuit
  512. contains any errors. All I can say is that it has not blown my
  513. amiga up yet. USE AT OWN RISK!
  514.  
  515. All numbers on the left are amiga signals. All numbers on the right
  516. are WD-1002-05 controller signals. Parts should be LS or better for speed.
  517. All signals marked * are active when low (inverted). Wires that cross like
  518.  
  519.          |
  520.      ---------
  521.          |
  522.  
  523. are not joined. Joins are shown by +'s as in
  524.  
  525.          |
  526.      ----+----
  527.          |
  528.  
  529.  
  530.            7404
  531.            1|\ 2   1|
  532. 74 AS* -----| >o----|
  533.             |/     2|
  534. 59 A23 -------------|
  535.            3|\ 4   3|
  536. 57 A22 -----| >o----|---\
  537.             |/      |    |8
  538.            5|\ 6   4|7430|o-----+
  539. 58 A21 -----| >o----|    |      |
  540.             |/     5|---/       |
  541. 56 A20 -------------|           |
  542.                    6|           |
  543. 54 A19 -------------|           |
  544.                   11|           |
  545. 52 A18 -------------|           |
  546.                   12|           |
  547. 47 A17 -------------|           |
  548.                     |           |     Address Decoding
  549.                                 |
  550.                    1|           |
  551. 45 A16 -------------|           |
  552.                    2|           |
  553. 43 A15 -------------|           |
  554.                    3|           |
  555. 41 A14 -------------|---\       |
  556.                    4|    |      |
  557. 39 A13 -------------|7430|o--+  |
  558.                    5|    |   |  |
  559. 38 A12 -------------|---/    |  |
  560.                    6|        |  |
  561. 36 A11 -------------|        |  |  ^
  562.                   11|        |  |  |
  563. 34 A10 -------------|       4o 5o 6|
  564.                   12|    +------------+
  565. 32 A9  -------------|    | E0*E1*E2 O7|o-7--$9FFFC0--+-------- CS* 23
  566.                     |    |          O6|o-9--$9FFF80  |
  567.                         3|          O5|o-10-$9FFF40  |
  568. 30 A8  ------------------|A2        O4|o-11-$9FFF00  |
  569.                         2|   74138  O3|o-12-$9FFEC0  |9
  570. 28 A7  ------------------|A1        O2|o-13-$9FFE80\---/
  571.                         1|          O1|o-14-$9FFE40 \ /7404
  572. 23 A6  ------------------|A0        O0|o-15-$9FFE00  o8
  573.                          +------------+              |
  574.                                                      |
  575.                     ^                     ^          |
  576.                     |                     |          |
  577.             ^      4o                   10o          |
  578.             |   +-------+             +-------+      |
  579.             |  2|   P   |5          12|   P   |9     |
  580.             +---|D     Q|-------------|D     Q|--    |
  581.                 | 7474  |             | 7474  |      |         Timing
  582. 16 C1 ------+---|>    Q*|o---    +----|>    Q*|o--------+
  583.             |  3|   C   |6       |  11|   C   |8     |  |
  584.             |   +-------+        |    +-------+      |  |
  585.             |      1o            |      13o          |  |
  586.             |       |            |        |          |  | 1
  587.             +--------------------+        |          |  +--|----\ 3
  588.                     |                     |          |     |7438 |o--+
  589.                     +---------------------+----------+-----|----/    |
  590.                                                           2 (openC)  |
  591.                                                                      |
  592. 18 XRDY* ------------------------------------------------------------+
  593.  
  594.                    13|\ 12     1      
  595. 68 R/W* ------+------| >o-------|----\ 3
  596.               |      |/        2|7400 |o--------- WR* 25
  597.               |              +--|----/
  598.               |    11|\ 10   |        
  599. 70 LDS* -------------| >o----+--|----\ 6
  600.               |      |/        4|7400 |o--------- RD* 27
  601.               +-----------------|----/
  602.                                5
  603.                         4
  604.                  6 /---|-----+
  605. 19 INT2* --------o|7438|     +------------------- INTRQ 35
  606.                    \---|-----+
  607.                 (openC) 5   Connect this only if want interrupts
  608.                             after every command is finished
  609.  
  610. 21 A5 ------- (not used)
  611. 24 A4 ------- (not used)
  612. 26 A3 ------- RS2 21
  613. 27 A2 ------- RS1 19
  614. 29 A1 ------- RS0 17
  615. 53 RES* ----- MR* 39
  616. 75 PD0 ------ DAL0 1
  617. 77 PD1 ------ DAL1 3
  618. 79 PD2 ------ DAL2 5
  619. 81 PD3 ------ DAL3 7
  620. 83 PD4 ------ DAL4 9
  621. 86 PD5 ------ DAL5 11
  622. 84 PD6 ------ DAL6 13
  623. 82 PD7 ------ DAL7 15
  624.  
  625.  
  626.  
  627. by Alan Kent
  628.